import serial
import modbus_tk.defines as cst
from modbus_tk import modbus_rtu
import struct
import config


# 定义一个modbus类
class PlcModbus:

    def __init__(self):
        try:
            # 串口配置
            self.__serial_config = serial.Serial(
                port=config.port,
                baudrate=config.baudrate,
                bytesize=config.bytesize,
                parity=config.parity,
                stopbits=config.stopbits
            )
            # 设置串口为主站
            self.__master = modbus_rtu.RtuMaster(self.__serial_config)
            self.__master.set_timeout(3.0)
            self.__master.set_verbose(True)
            self.serial_connect = True  # 连接状态
        except Exception:
            print('串口打开失败')
            self.serial_connect = False

    def __del__(self):
        # 关闭串口,不用try时，在没有打开串口时，会报错。
        try:
            self.__master.close()
        except:
            pass

    # 读单个寄存器
    def read_int(self, reg_address):
        read = self.__master.execute(1, cst.READ_HOLDING_REGISTERS, reg_address, 1)
        # 当数据为负值时，结果是错误的。参考资料：https://www.cnblogs.com/yqmcu/p/11104617.html解决了这个问题
        if read[0] < 0x8000:
            return read[0]
        else:
            return read[0] - 0x10000

    # 读一个位,返回 True 或 False
    def read_bit(self, reg_address):
        read = self.__master.execute(1, cst.READ_COILS, reg_address, 1)
        return bool(read[0])

    # 写单个寄存器
    def write_int(self, reg_address, value):
        self.__master.execute(1, cst.WRITE_SINGLE_REGISTER, reg_address, output_value=value)

    # 写一个位
    def write_bit(self, reg_address, value):
        self.__master.execute(1, cst.WRITE_SINGLE_COIL, reg_address, output_value=value)

    # 读取32位整数
    def read_dint(self, reg_address):
        read = self.__master.execute(1, cst.READ_HOLDING_REGISTERS, reg_address, 2)
        # 数据要组合成1个32位的整数
        n, m = '%04x' % read[0], '%04x' % read[1]  # 一个数据格式化成4位16进制的数。
        y_bytes = bytes.fromhex(m + n)  # 把合成的8位16进制型的字符串转换成bytes数组,b'\x124Vx'
        y = struct.unpack('!i', y_bytes)[0]  # 解包成整数
        return y

    # 写32位整数
    def write_dint(self, reg_address, value):
        # 先把value转化成两个INT的列表
        y_bytes = struct.pack('!i', value)
        y_hex = ''.join(['%02x' % i for i in y_bytes])
        n, m = y_hex[:-4], y_hex[-4:]
        n, m = int(n, 16), int(m, 16)
        # 写入PLC
        self.__master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, reg_address, output_value=[m, n])

    # 读取一个32位浮点数
    def read_float(self, reg_address):
        read = self.__master.execute(1, cst.READ_HOLDING_REGISTERS, reg_address, 2)
        # 数据要组合成1个32位的浮点数
        n, m = '%04x' % read[0], '%04x' % read[1]  # 一个数据格式化成4位16进制的数。
        y_bytes = bytes.fromhex(m + n)  # 把合成的8位16进制型的字符串转换成bytes数组,b'\x124Vx'
        y = struct.unpack('!f', y_bytes)[0]  # 解包成浮点数
        return round(y, 6)  # 结果保留6个小数位

    # 写一个32位的浮点数：跟32位整数差别非常小
    def write_float(self, reg_address, value):
        # 先把value转化成两个INT的列表
        y_bytes = struct.pack('!f', value)
        y_hex = ''.join(['%02x' % i for i in y_bytes])
        n, m = y_hex[:-4], y_hex[-4:]
        n, m = int(n, 16), int(m, 16)
        # 写入PLC
        self.__master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, reg_address, output_value=[m, n])

    # 读取字符串
    def read_string(self, reg_address, reg_length):
        read = self.__master.execute(1, cst.READ_HOLDING_REGISTERS, reg_address, reg_length)
        # 一个寄存器里有2个字母，得分别查ASCII
        str_name = ""
        for n in read:
            chr1 = n & 0xFF
            chr2 = n >> 8
            if chr1 != 0:
                str_name += chr(chr1)
            if chr2 != 0:
                str_name += chr(chr2)
        return str_name


if __name__ == '__main__':
    md = PlcModbus()
    if md.serial_connect == True:
        print("串口打开成功！")
